package base.thread_demo.demo05;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 死锁演示
 *  出现死锁场景之一： 同步嵌套，简单点就是说，你中有我，我中有你，互相持有对方需要的“同一把”锁。
 *  注意：与是否是同一个任务对象没有关系.
 *  账户1：100
 *  账户2: 200
 *  线程1：账户1往账户2转账200
 *  线程2：账户2往账户1转账300  该程序会出现死锁
 *
 * Object类中提供了三个方法：
 * 	wait():等待
 * 	notify():唤醒单个线程,仅仅适合单生产者单消费者情况
 * 	notifyAll():唤醒所有线程，多生产者多消费者必须用notifyAll唤醒，否则唤醒的可能还是自己方的线程，进而出现都进入wait的僵死状态
 * 	为什么这些方法不定义在Thread类中呢?
 * 		因为等待或者唤醒的是该对象锁上的线程，只有对象锁知道自己身上有多少锁。
 * 		这些方法的调用必须通过锁对象调用，而我们刚才使用的锁对象是任意对象。
 * 		所以，这些方法必须定义在Object类中。
 *
 */
public class Deadlock {
    public static void main(String[] args) {
        ThreadDemo d1 = new ThreadDemo(true);
        ThreadDemo d2 = new ThreadDemo(false);

        new Thread(d1,"t1").start();
        new Thread(d2,"t2").start();
    }

}

class MyLock {
    public static final Object lockA = new Object();
    public static final Object lockB = new Object();
}

class ThreadDemo implements Runnable{
    private static final Logger log = LoggerFactory.getLogger(ThreadDemo.class);
    private boolean flag;
    public ThreadDemo(boolean flag) {
        this.flag = flag;
    }

    /**
     * 	同步代码的嵌套是导致死锁的根本原因。
     * 	1线程持有A锁，2线程持有B锁，1线程又要拿到B锁才可将任务完成，同时2线程又要拿到A锁才可将任务完成，因此出现
     * 	1要拿挂在2上的锁，2要拿挂在1上的锁，就出现了死锁。
     */
    @Override
    public void run() {
        while(true) {
            //让线程一在执行任务过程中持有A、B锁
            if(flag) {
                synchronized(MyLock.lockA) {
                    log.info("{} lockA", Thread.currentThread().getName());//假设线程1走到这里，线程2获得CPU执行权，A锁未被线程1释放，线程2进入else。
                    synchronized (MyLock.lockB) {//线程1继续执行到这里发现获取不到B锁，出现等待获取B锁，假设CPU执行权又交给线程2
                        log.info("{} lockB", Thread.currentThread().getName());
                    }
                }
            }else {
                //让线程二在执行任务过程中持有B、A锁，这样就与线程一的任务就发生锁的嵌套
                synchronized(MyLock.lockB) {
                    log.info("{} lockB", Thread.currentThread().getName());//假设线程2执行到这里，线程1获得CPU执行权，B锁未被线程2释放，此时线程1继续if操作
                    synchronized (MyLock.lockA) {//线程2执行到这里，需要获取A锁，发现获取不到A锁，出现等待，此时就出现死锁了
                        log.info("{} lockA", Thread.currentThread().getName());
                    }
                }
            }
        }
    }

}

